﻿/* ================================================================================
<copyright file="LogPublisher.cs" company="The Framework Factory, LLC" lastWriteTime="12/05/2012 23:23:47 PM">
  Copyright (c) 2012 The Framework Factory, LLC.  All rights reserved.
</copyright>
=================================================================================== */
using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Threading;

namespace FWF.Logging.Net
{
    /// <summary>
    /// An implementation of the <see cref="ILoggerListener"/> that will pubish all 
    /// observations to remote endpoints.  Each remote endpoint is required to subscribe 
    /// to receive events using <see cref="ILogPublisher"/>.  See the Logger View
    /// utility for more details.
    /// </summary>
    [Serializable]
    public class LogPublisher :
        MarshalByRefObject,
        ILoggerListener,
        ILogPublisher
    {
        private static Uri _listenUrl;

        private static readonly List<SubscriptionItem> _subscriptions = new List<SubscriptionItem>();
        private static readonly List<string> _availableLogs = new List<string>();
        private static readonly Queue<LogEventItem> _items = new Queue<LogEventItem>();

        private const int MaxItemsAtATime = 100;

        private static readonly IFWFLog _log = LogFactory.Get( typeof( LogPublisher ) );

        private static Thread _thread;
        private static readonly ManualResetEvent _resetEvent = new ManualResetEvent( false );
        private static readonly TimeSpan _autoContinueLatency = TimeSpan.FromMilliseconds( 400 );

        private static volatile object _lockObject = new object();

        /// <summary>
        /// 
        /// </summary>
        private class SubscriptionItem : IEquatable<SubscriptionItem>
        {
            /// <summary>
            /// Gets or sets the URL.
            /// </summary>
            /// <value>The URL.</value>
            public Uri Url
            {
                get;
                private set;
            }

            ///// <summary>
            ///// Gets or sets the subscriptions.
            ///// </summary>
            ///// <value>
            ///// The subscriptions.
            ///// </value>
            //public ICollection<SubscriptionLogItem> Subscriptions
            //{
            //    get;
            //    set;
            //}

            /// <summary>
            /// Gets or sets the instance.
            /// </summary>
            /// <value>The instance.</value>
            public ILogSubscriber Instance
            {
                get;
                set;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="SubscriptionItem"/> class.
            /// </summary>
            /// <param name="url">The URL.</param>
            public SubscriptionItem( Uri url )
            {
                Url = url;
                //this.Subscriptions = new List<SubscriptionLogItem>();
            }

            #region IEquatable<SubscriptionItem> Members

            /// <summary>
            /// Indicates whether the current object is equal to another object of the same type.
            /// </summary>
            /// <param name="other">An object to compare with this object.</param>
            /// <returns>
            /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
            /// </returns>
            public bool Equals( SubscriptionItem other )
            {
                if ( ReferenceEquals( other, null ) )
                {
                    return false;
                }

                return Url == other.Url;
            }

            #endregion
        }

        ///// <summary>
        ///// 
        ///// </summary>
        //public class SubscriptionLogItem
        //{
        //    /// <summary>
        //    /// 
        //    /// </summary>
        //    public string LogPrefix
        //    {
        //        get;
        //        set;
        //    }
        
        //    /// <summary>
        //    /// 
        //    /// </summary>
        //    public int LogLevel
        //    {
        //        get;
        //        set;
        //    }
        
        //}

        #region Constructors / Finalizer

        /// <summary>
        /// Initializes a new instance of the <see cref="LogPublisher"/> class.
        /// </summary>
        public LogPublisher()
        {
            //
            ObserverAppender.AddHandler( this );

            lock ( _lockObject )
            {
                // Create the thread only once
                if ( _thread == null )
                {
                    //
                    _thread = new Thread( DoWork );
                    _thread.Name = "LogPublisher Processing Thread";
                    _thread.IsBackground = true;
                    _thread.Start();
                }
            } 
        }

        #endregion

        #region ILoggerListener Members

        /// <summary>
        /// Called when [receive message].
        /// </summary>
        /// <param name="logEventItem">The logging event item.</param>
        public void Log(LogEventItem logEventItem)
        {
            // Do not log if there are no subscribers
            if ( _subscriptions.Count == 0 )
            {
                return;
            }

            //
            lock ( _lockObject )
            {
                if ( !_availableLogs.Contains( logEventItem.LoggerName ) )
                {
                    _availableLogs.Add( logEventItem.LoggerName );
                }
            }

            _items.Enqueue( logEventItem );
        }

        #endregion

        #region ITestRemoteCall Members

        /// <summary>
        /// Pings this instance.
        /// </summary>
        /// <returns></returns>
        public bool Ping()
        {
            return true;
        }

        #endregion

        #region ILogPublisher Members

        /// <summary>
        /// Adds the subscription.
        /// </summary>
        /// <param name="url">The URL.</param>
        public void AddSubscription( Uri url )
        {
            SubscriptionItem itm = _subscriptions.Find(
                x => x.Url.Equals( url )
                );

            lock ( _lockObject )
            {
                if ( itm == null )
                {
                    itm = new SubscriptionItem( url );

                    // Use RemotingServices to connect to the url given as input
                    itm.Instance = (ILogSubscriber) RemotingServices.Connect(
                        typeof(ILogSubscriber), 
                        url.ToString()
                        );

                    //
                    try
                    {
                        // Try a basic remote call to test the endpoint is available
                        itm.Instance.Ping();

                        // If no errors, then allow for the subscription to be added
                        _subscriptions.Add( itm );
                    }
                    catch( Exception ex )
                    {
                        _log.Error(
                            string.Format( 
                                "Unable to add subscription to LogPublisher, invalid uri: {0}",
                                url
                                ), 
                            ex 
                            );
                    }
                }
            }
        }

        /// <summary>
        /// Removes the subscription.
        /// </summary>
        /// <param name="url">The URL.</param>
        public void RemoveSubscription( Uri url )
        {
            var itm = new SubscriptionItem( url );

            lock ( _lockObject )
            {
                if ( _subscriptions.Exists(
                    x => x.Equals( itm )
                    ) )
                {
                    _subscriptions.Remove( itm );
                }
            }
        }

        #endregion

        /// <summary>
        /// Registers the and listen.
        /// </summary>
        public static int RegisterAndListen()
        {
            //
            _listenUrl = PortLib.RegisterNextAvailableChannel( "LogPublisher" );

            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof( LogPublisher ),
                "LogPublisher",
                WellKnownObjectMode.Singleton
                );

            return _listenUrl.Port;
        }

        /// <summary>
        /// Executes the background logic to push all observation data to the remote subscribers.
        /// </summary>
        /// <param name="sender">The sender.</param>
        private void DoWork( object sender )
        {
            var listMessageEvents = new List<LogEventItem>();

            while ( true )
            {
                listMessageEvents.Clear();

                int index = 0;

                lock (_lockObject)
                {
                    while (_items.Count > 0)
                    {
                        LogEventItem itm = _items.Dequeue();

                        // Add to list to be sent out
                        listMessageEvents.Add(itm);

                        index++;

                        if (index > MaxItemsAtATime)
                        {
                            break;
                        }
                    }
                }

                lock (_lockObject)
                {
                    for (int i = _subscriptions.Count - 1; i >= 0; i--)
                    {
                        SubscriptionItem itm = _subscriptions[i];

                        // Send all events
                        foreach (LogEventItem queueItm in listMessageEvents)
                        {
                            //
                            if (!ShouldLogForSubscription(queueItm, itm))
                            {
                                continue;
                            }

                            try
                            {
                                itm.Instance.LogEvent(
                                        queueItm.TimeStamp,
                                        queueItm.MachineName,
                                        queueItm.InstanceName,
                                        queueItm.LoggerName,
                                        queueItm.LevelValue,
                                        queueItm.LevelName,
                                        queueItm.ClassName,
                                        queueItm.FileName,
                                        queueItm.LineNumber,
                                        queueItm.MethodName,
                                        queueItm.SessionId,
                                        queueItm.SessionName,
                                        queueItm.SessionContext,
                                        queueItm.Message,
                                        queueItm.ExceptionType,
                                        queueItm.ExceptionMessage,
                                        queueItm.ThreadName,
                                        queueItm.UserName
                                        );

                            }
                            catch (Exception ex)
                            {
                                _log.WarnFormat("Unable to send MessageEvent history: {0}", ex.Message);

                                // If a observer causes an error, remote the observer
                                RemoveSubscription(itm.Url);

                                break;
                            }
                        }
                    }
                }

                bool signaled = _resetEvent.WaitOne(_autoContinueLatency);

                if ( signaled)
                {
                    break;
                }
            }
            

        }

        /// <summary>
        /// Shoulds the log for subscription.
        /// </summary>
        /// <param name="logEventItem">The logging event item.</param>
        /// <param name="subscriptionItem">The subscription item.</param>
        /// <returns></returns>
        private bool ShouldLogForSubscription( LogEventItem logEventItem, SubscriptionItem subscriptionItem )
        {

            // TODO: Implement detailed subscriptions
            return true;

            //foreach ( SubscriptionLogItem subscriptionLogItem in subscriptionItem.Subscriptions )
            //{
            //    //
            //    if ( LogEventItem.LoggerName.StartsWith( subscriptionLogItem.LogPrefix ) )
            //    {
            //        //
            //        if ( LogEventItem.LevelValue >= subscriptionLogItem.LogLevel )
            //        {
            //            return true;
            //        }
            //    }
            //}

            //return false;
        }

    }
}
